package raft

import "6.824/utils"

//
// example code to send a RequestVote RPC to a server.
// server is the index of the target server in rf.peers[].
// expects RPC arguments in args.
// fills in *reply with RPC reply, so caller should
// pass &reply.
// the types of the args and reply passed to Call() must be
// the same as the types of the arguments declared in the
// handler function (including whether they are pointers).
//
// The labrpc package simulates a lossy network, in which servers
// may be unreachable, and in which requests and replies may be lost.
// Call() sends a request and waits for a reply. If a reply arrives
// within a timeout interval, Call() returns true; otherwise
// Call() returns false. Thus Call() may not return for a while.
// A false return can be caused by a dead server, a live server that
// can't be reached, a lost request, or a lost reply.
//
// Call() is guaranteed to return (perhaps after a delay) *except* if the
// handler function on the server side does not return.  Thus there
// is no need to implement your own timeouts around Call().
//
// look at the comments in ../labrpc/labrpc.go for more details.
//
// if you're having trouble getting RPC to work, check that you've
// capitalized all field names in structs passed over RPC, and
// that the caller passes the address of the reply struct with &, not
// the struct itself.
//
func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {
	ok := rf.peers[server].Call("Raft.RequestVote", args, reply)
	return ok
}

//
// example RequestVote RPC handler.
//
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {

	rf.begin()
	defer rf.end()
	rf.Log("INFO", "Rec ReqVote from %d. candidate term:%d, lastlogindex:%d lastlogterm:%d", args.CandidateID, args.Term, rf.persistentContext.GetLastIndex(), rf.getLastLogTerm())
	if args.Term < rf.persistentContext.CurrentTerm {
		reply.Term = rf.persistentContext.CurrentTerm
		reply.VoteGranted = false
		return
	}
	reply.Term = args.Term
	reply.VoteGranted = false
	rf.handleOthersTerm(args.Term)

	//need compare the log's version
	if rf.persistentContext.VotedFor == -1 || rf.persistentContext.VotedFor == args.CandidateID {
		lstterm := rf.getLastLogTerm()
		if lstterm < args.LastLogTerm || (lstterm == args.LastLogTerm && rf.persistentContext.GetLastIndex() <= args.LastLogIndex) {
			rf.Log("VOTE", "Vote for %d", args.CandidateID)
			reply.VoteGranted = true
			rf.WriteVoted(args.CandidateID)
			rf.resetTimeoutClock()
		}

	}
}

func (rf *Raft) sendRequestAppend(server int, args *RequestAppendArgs, reply *RequestAppendReply) bool {
	ok := rf.peers[server].Call("Raft.RequestAppend", args, reply)
	return ok
}

//
// example RequestVote RPC handler.
//
func (rf *Raft) RequestAppend(args *RequestAppendArgs, reply *RequestAppendReply) {
	rf.begin()
	defer rf.end()

	rf.handleOthersTerm(args.Term)
	if args.Term < rf.persistentContext.CurrentTerm {
		reply.Term = rf.persistentContext.CurrentTerm
		reply.Success = false

		rf.Log("TRCE", "rec append, ret false thus outdated term")
		return
	} else {
		utils.Assert(rf.persistentContext.CurrentTerm == args.Term)
		if rf.currentState != FollowerState {
			rf.changeToState(FollowerState, args.Term)
		}
		rf.resetTimeoutClock()
		rf.follower.recvAppend(args, reply)

	}
}
func (rf *Raft) sendRequestInstall(server int, args *RequestInstallSnapshotArgs, reply *RequestInstallSnapshotReply) bool {
	ok := rf.peers[server].Call("Raft.RequestInstall", args, reply)
	return ok
}

//
// example RequestVote RPC handler.
//
func (rf *Raft) RequestInstall(args *RequestInstallSnapshotArgs, reply *RequestInstallSnapshotReply) {
	rf.begin()
	defer rf.end()
	rf.handleOthersTerm(args.Term)

	reply.Term = rf.persistentContext.CurrentTerm

	if rf.persistentContext.CurrentTerm > args.Term {
		return
	}
	if args.LastIncludedIndex <= rf.persistentContext.GetLastIndex() &&
		(rf.persistentContext.SnapshotIndex >= args.LastIncludedIndex ||
			rf.persistentContext.GetEntryTerm(args.LastIncludedIndex) == args.LastIncludedTerm) {
		return
	} else {
		utils.Assert(rf.commitIndex <= args.LastIncludedIndex)
		rf.persistentContext.LogEntries = rf.persistentContext.LogEntries[0:0]
		rf.commitIndex = args.LastIncludedIndex
		rf.persistentContext.SnapshotIndex = args.LastIncludedIndex
		rf.persistentContext.SnapshotTerm = args.LastIncludedTerm
		rf.needPersist = true
		rf.snapshotToPersist = args.Snapshot
	}
}
